package com.swills.mandelbrot;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JPanel;

/**
 * Plots the population equation p(n+1) = r * p(n) * (1 - p(n)) where r is the
 * rate of growth of the population. r will vary between 0 and 4.0. The x-axis
 * will represent r, the y-axis will represent the population size after n
 * iterations (initially try n = 100).
 * 
 * Map the x-axis points to values of r (initially from 0 to 4.0). x-axis points
 * will start from x = 100 to width - 100
 * 
 * @author stephen
 * 
 */
public class Population extends JPanel {

	private static final int Y_START = 100;
	private static final int X_START = 100;
	private static final int ITERATIONS_SAVED_PER_X = 600;
	private int xWidth;
	private Point mousePressedPnt = null;
	private Point mouseReleasePnt = null;
	private double rInitial;
	private double rFinal;
	private double pMin;
	private double pMax;

	public Population() {
		this.setBackground(Color.WHITE);
		rInitial = 0.0;
		rFinal = 4.0;
		pMin = 0.0;
		pMax = 1.0;
		wire();
	}

	public void resetPopulation() {
		rInitial = 0.0;
		rFinal = 4.0;
		pMin = 0.0;
		pMax = 1.0;
		repaint();
	}

	private void wire() {
		this.addMouseListener(new MouseAdapter() {

			@Override
			public void mousePressed(MouseEvent e) {

				super.mousePressed(e);
				mousePressedPnt = e.getPoint();
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				super.mouseReleased(e);

				mouseReleasePnt = e.getPoint();
				calcNewRandPRange();
				repaint();
			}

			/**
			 * Based on the previous min and max r and p values and the given
			 * min and max y values of the selected region, calculates new r and
			 * p max / min values.
			 */
			private void calcNewRandPRange() {
				{ // calc new r min / max
					int newX2;
					int newX1;
					if (mouseReleasePnt.x > mousePressedPnt.x) {
						newX1 = mousePressedPnt.x;
						newX2 = mouseReleasePnt.x;
					} else {
						newX1 = mouseReleasePnt.x;
						newX2 = mousePressedPnt.x;
					}
					newX1 = newX1 <= X_START ? X_START : (newX1 -= X_START);
					newX2 -= X_START;

					double gap = rFinal - rInitial;
					rFinal = rInitial + gap * newX2 / xWidth;
					rInitial = rInitial + gap * newX1 / xWidth;
				}

				{ // calc new p min / max
					int newY2;
					int newY1;
					if (mouseReleasePnt.y > mousePressedPnt.y) {
						newY1 = mousePressedPnt.y;
						newY2 = mouseReleasePnt.y;
					} else {
						newY1 = mouseReleasePnt.y;
						newY2 = mousePressedPnt.y;
					}

					double yGap = getHeight() - 2 * Y_START;
					double pGap = pMax - pMin;
					pMax = pMin + (yGap - (newY1 - Y_START)) / yGap * pGap;
					pMin = pMin + (yGap - (newY2 - Y_START)) / yGap * pGap;
					System.out.println("pMin = " + pMin + "; pMax = " + pMax);
					int u = 0;
				}

			}
		});
	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		xWidth = getWidth() - 2 * X_START;
		double[][] points = calcPopulationPoints();

		plotAxis(g);
		plot(g, points);
	}

	private void plotAxis(Graphics g) {
		// x-axis
		int xTics = 4;
		int xSpace = Math.round(xWidth / xTics);
		int yAxis = getHeight() - Y_START;

		g.setColor(Color.RED);
		g.drawLine(X_START, yAxis, X_START + xWidth, yAxis);

		for (int x = 0; x <= xTics; x++) {
			g.drawLine(X_START + xSpace * x, yAxis + 5, X_START + xSpace * x,
					yAxis - 5);
			g.drawString(
					Double.toString(rInitial + x * (rFinal - rInitial) / xTics),
					X_START + xSpace * x - 3, yAxis + 17);
		}
		g.drawString("Growth Rate", X_START + xWidth - 72, yAxis + 30);

		// y-axis
		g.drawLine(X_START, getHeight() - 100, X_START, 100);

		g.drawString(Double.toString(pMax), X_START - 50, Y_START);
		g.drawString(Double.toString(pMin), X_START - 50, getHeight() - Y_START);
	}

	private void plot(Graphics g, double[][] points) {
		g.setColor(Color.BLACK);
		int yHeight = getHeight() - 2 * Y_START;
		int yStart = getHeight() - Y_START;
		for (int x = 0; x < points.length; x++) {
			for (int m = 0; m < ITERATIONS_SAVED_PER_X; m++) {
				int y = (int) Math
						.round(((points[x][m] - pMin) / (pMax - pMin))
								* yHeight);
				g.drawLine(X_START + x, yStart - y, X_START + x, yStart - y);
			}
		}
	}

	private double[][] calcPopulationPoints() {
		double rGap = rFinal - rInitial;
		double[][] points = new double[xWidth][ITERATIONS_SAVED_PER_X];

		for (int x = 0; x < xWidth; x++) {
			double r = rInitial + x * rGap / xWidth;
			double y = 0.5;
			y = r * y * (1 - y);
			for (int n = 1; n < 1000; n++) {
				y = r * y * (1 - y);
			}
			for (int m = 0; m < ITERATIONS_SAVED_PER_X; m++) {
				y = r * y * (1 - y);
				if (isInRange(y)) {
					points[x][m] = y;
				}
			}
		}
		return points;
	}

	/**
	 * Checks if a point value should plotted based on the given y value and the
	 * y-axis range indicating the current population range.
	 */
	private boolean isInRange(double y) {
		return (y >= pMin && y <= pMax);
	}

}
